import secrets
import string
from datetime import datetime, timedelta
from typing import Any, List, Optional, Tuple

from aiogram.client.session.aiohttp import ClientSession
from dateutil.relativedelta import relativedelta
from sqlalchemy.ext.asyncio import AsyncSession

from app.bot.services.loggers import logger
from app.database.enums import OrderPeriod
from app.database.models import Category, Proxy, Server
from app.database.services.servers import get_server


async def get_expiring_on(date: datetime, period: str) -> datetime:

    if period == OrderPeriod.twenty_minutes.value:
        expiring_on = date + timedelta(minutes=20)
    if period == OrderPeriod.four_hours.value:
        expiring_on = date + timedelta(hours=4)
    elif period == OrderPeriod.one_day.value:
        expiring_on = date + timedelta(days=1)
    elif period == OrderPeriod.one_week.value:
        expiring_on = date + timedelta(days=7)
    elif period == OrderPeriod.two_weeks.value:
        expiring_on = date + timedelta(days=14)
    elif period == OrderPeriod.one_month.value:
        expiring_on = date + relativedelta(months=1)

    return expiring_on


async def get_price_by_category(
    period: str, category: Category, selected_amount: int
) -> float:

    if period == OrderPeriod.four_hours.value:
        order_summ = round(category.price_4h * selected_amount, 2)
    elif period == OrderPeriod.one_day.value:
        order_summ = round(category.price_1d * selected_amount, 2)
    elif period == OrderPeriod.one_week.value:
        order_summ = round(category.price_1w * selected_amount, 2)
    elif period == OrderPeriod.two_weeks.value:
        order_summ = round(category.price_2w * selected_amount, 2)
    elif period == OrderPeriod.one_month.value:
        order_summ = round(category.price_1m * selected_amount, 2)

    return order_summ


async def generate_proxy_string(
    proxy_user: str, proxy_password: str, proxy_port: int, modem_ip: str, inner_ip: str
) -> str:
    return (
        f"auth strong\n"
        f"users {proxy_user}:CL:{proxy_password}\n"
        f"allow {proxy_user}\n"
        f"deny {proxy_user} * $/usr/local/3proxy/banlist.list * * *\n"
        f"proxy -n -a -p{proxy_port} -i{inner_ip} -e{modem_ip}\n"
        f"flush"
    )

    # Блок с кэшированием (пока отключен)
    # return (
    #     f"auth strong\n"
    #     f"users {proxy_user}:CL:{proxy_password}\n"
    #     f"allow {proxy_user} * * * * {proxy_port}\n"
    #     f"deny {proxy_user} * $/usr/local/3proxy/banlist.list  * * * 1001 {proxy_port}\n"
    #     f"allow * * * {proxy_port}\n"
    #     f"parent 1000 http 5.9.95.220 3030 admin R2B3GJT7dQG\n"
    #     f"proxy -n -a -p{proxy_port} -i192.168.99.149 -e{modem_ip}\n"
    #     f"flush"
    # )


async def generate_iponly_proxy_string(
    ip_address: str, proxy_port: int, modem_ip: str
) -> str:
    return (
        f"auth iponly\n"
        f"allow * {ip_address}\n"
        f"proxy -n -a -p{proxy_port} -i192.168.99.149 -e{modem_ip}\n"
        f"flush"
    )


async def generate_reload_file_content(server: Server, modem_ip: str) -> str:
    return (
        f"#! /bin/bash\n"
        f"<?php \n"
        f'$connection = ssh2_connect("192.168.99.149", 22);\n\n'
        f"if(!$connection) throw new Exception('Не удается подключиться к серверу');\n"
        f"if(!ssh2_auth_password($connection, '{server.login}', '{server.password}')) throw new Exception('Неверный пользователь или пароль');\n\n"
        f"$script = '/root/rekonekt.sh -r 4G  -i {modem_ip.replace('00', '')}';\n"
        f"$stream = ssh2_exec($connection, $script);\n"
        f"stream_set_blocking($stream, true);\n"
        f"$stream_out = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO);\n"
        f"echo $result = stream_get_contents($stream_out);\n"
        f"?>\n"
    )


async def generate_proxy() -> Tuple[str, str, str]:
    alphabet = string.ascii_letters + string.digits
    login = "".join(secrets.choice(alphabet) for i in range(8))
    password = "".join(secrets.choice(alphabet) for i in range(8))
    reload_link = secrets.token_urlsafe(16)

    return login, password, reload_link


async def get_server_proxies(
    db_session: AsyncSession,
    proxies: List[Proxy] | List[dict[str | Any]],
    new_proxies: Optional[List[Proxy]] = None,
) -> dict[Server, List[Proxy]] | dict[Server, Tuple[Proxy, Proxy]]:
    """
    Returns the dict containing the server and proxy belonging to it.
    Also can return dict also containing new proxies belonging to server
    :param AsyncSession db_session: SQLAlchemy session
    :param List[Proxy] | List[dict[str | Any]] proxies: list of Proxy instances or list of dicts
    :param Optional[List[Proxy]] new_proxies: list of Proxy instances with new credentials
    """

    servers = []
    if isinstance(proxies[0], dict):
        server_ids = [proxy.get("server_id") for proxy in proxies]
    else:
        server_ids = [proxy.server_id for proxy in proxies]
    server_ids = list(set(server_ids))

    for server_id in server_ids:
        server = await get_server(db_session, server_id)
        servers.append(server)

    server_proxies = {}
    for server in servers:
        if new_proxies:
            # Объединяем в один словарь старые и новые прокси, которые относятся к определенному серверу
            server_proxies[server] = [
                (new_proxy, old_proxy)
                for old_proxy, new_proxy in zip(proxies, new_proxies)
                if old_proxy.get("server_id") == server.id
                and new_proxy.server == server
            ]
        else:
            server_proxies[server] = [
                proxy for proxy in proxies if server == proxy.server
            ]

    return server_proxies


async def get_rate_usd() -> float:
    url = "https://www.cbr-xml-daily.ru/daily_json.js"
    try:
        async with ClientSession() as session:
            async with session.get(url, verify_ssl=False) as req_:
                res = await req_.json(content_type=None)
        return res.get("Valute").get("USD").get("Value")
    except Exception as e:
        logger.exception(e)
    return 0
